13.2 精通自定义 View 之控件高级属性——Window 与 WindowManager

返回自定义 View 目录

Window 表示窗口,在某些特殊的时候,比如需要在桌面或者锁屏上显示一些类似悬浮窗的效果,就需要用到 Window。Android 中所有的视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上的。而 WindowManager 则提供了对这些 Window 的统一管理功能。

13.2.1 Window 与 WindowManager 的联系

为了分析 Window 的工作机制,我们需要了解如何使用 WindowManager 来添加一个 Window。

1
2
3
WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(width, height, type, flags, format);
manager.addView(btn, layoutParams);

上面的伪代码看起来非常简单,在构建 WindowManager.LayoutParams 时,其中的 type 和 flags 参数比较重要。

flags 参数有很多选项,用来控制 Window 的显示特性。我们来看几个常用的选项。

1
public static final int FLAG_NOT_FOCUSABLE = 0x00000008;

表示此 Window 不需要获取焦点,不接收各种输入时间,此标记会同时启用 FLAG_NOT_TOUCH_MODEL,最终事件会直接传递给下层具有焦点的 Window。

1
public static final int FLAG_NOT_TOUCH_MODEL = 0x00000020;

自己 Window 区域内的事件自己处理;自己 Window 区域外的事件传递给底层 Window 处理。一般这个选项会默认开启,否则其他 Window 无法接收事件。

1
public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;

可以让此 Window 显示在锁屏上。

type 参数是 int 类型的,表示 Window 的类型。Window 有三种类型:应用 Window、子 Window 和系统 Window。应用 Window 对应着一个 Activity。子 Window 不能独立存在,它需要附属在特定的父 Window 中,比如 Dialog 就是一个子 Window。系统 Window 是需要声明权限才能创建的,比如 Toast 和系统状态栏都是系统 Window。

Window 是分层的,层级大的 Window 会覆盖在层级小的 Window 上面。

  • 应用 Window 的层级范围:1 ~ 99。
  • 子 Window 的层级范围:1000 ~ 1999。
  • 系统 Window 的层级范围:2000 ~ 2999。

type 参数就对应这些数字。如果想让 Window 置于顶层,则采用较大的层级即可;如果是系统类型的 Window,则需要在 AndroidMenifest.xml 中配置如下权限声明,否则会报权限不足的错误。

1
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

WindowManager 提供的功能很简单,常用的只有三个方法,即添加 View、更新 View 和删除 View。这三个方法定义在 ViewManager 中,而 WindowManager 继承自 ViewManager。

1
2
3
4
5
6
7
8
9
@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
}
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}

13.2.2 示例:悬浮窗

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnClickListener {
private ImageView mImageView;
private WindowManager.LayoutParams mLayoutParams;
private WindowManager mWindowManager;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, 100);
}
initView();
}
private void initView() {
findViewById(R.id.add_btn).setOnClickListener(this);
findViewById(R.id.rmv_btn).setOnClickListener(this);
mWindowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
}
@Override
public void onClick(View v) {
if (v.getId() == R.id.add_btn) {
mImageView = new ImageView(this);
mImageView.setBackgroundResource(R.mipmap.ic_launcher_round);
mLayoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
2099,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
PixelFormat.TRANSPARENT );
mLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
mLayoutParams.gravity = Gravity.TOP | Gravity.LEFT;
mLayoutParams.x = 0;
mLayoutParams.y = 300;
mImageView.setOnTouchListener(this);
mWindowManager.addView(mImageView, mLayoutParams);
} else if (v.getId() == R.id.rmv_btn) {
if (mWindowManager != null && mImageView != null) {
mWindowManager.removeViewImmediate(mImageView);
}
}
}
@Override
public boolean onTouch(View v, MotionEvent event) {
int rawX = (int) event.getRawX();
int rawY = (int) event.getRawY();
if (event.getAction() == MotionEvent.ACTION_MOVE) {
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mImageView, mLayoutParams);
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 100) {
initView();
}
}
}